package com.such.kit.database.migration;

import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import com.alibaba.fastjson.JSON;
import com.such.kit.Callable;
import com.such.kit.bean.BeanCommons;
import com.such.kit.database.JdbcCommons;
import com.such.kit.database.MetaDataCommons;
import com.such.kit.database.bean.DDLTable;
import com.such.kit.database.bean.DDLTableKey;
import com.such.kit.database.bean.MigrationCompareResult;
import com.such.kit.database.bean.MigrationConfig;
import com.such.kit.database.bean.MigrationExtractResult;
import com.such.kit.database.bean.MigrationTableFilter;
import com.such.kit.database.dialect.DialectCommons;
import com.such.kit.easyparam.ParamMap;
import com.such.kit.file.FileCommons;
import com.such.kit.file.bean.FileInfo;
import com.such.kit.file.support.FileFilter;
import com.such.kit.io.bean.SqlIOConfig;
import com.such.kit.io.template.JSONIOTemplate;
import com.such.kit.io.template.SqlIOTemplate;
import com.such.kit.io.source.DatabaseIOSource;
import com.such.kit.io.source.MemoryIOSource;
import com.such.kit.placeholder.PlaceholderCommons;
import com.such.kit.stream.StreamCommons;
import com.such.kit.string.StringCommons;
import com.such.kit.validate.ValidateCommons;

/**
 * @author SUCH
 * <pre>
 * 数据迁移分为 3 部分功能
 * 1. 数据抽取
 * 2. 数据比较
 * 3. 数据同步
 * </pre>
 */
public class MigrationCommons {

	private MigrationConfig config;
	private String workDir;
	private DataSource fromDataSource;
	private String fromFileSource;
	private MigrationExtractResult fromExtractResult;
	private DataSource toDataSource;
	private MigrationExtractResult toExtractResult;

	/**
	 * <p>构造函数</p>
	 * @param workDir 工作目录
	 * @param fromDataSource 来源
	 * @param toDataSource 去向
	 * @param config 设置
	 */
	public MigrationCommons(String workDir, DataSource fromDataSource, DataSource toDataSource, MigrationConfig config) {
		this.config = config;
		this.workDir = workDir;
		this.fromDataSource = fromDataSource;
		this.toDataSource = toDataSource;
	}

	/**
	 * <p>构造函数</p>
	 * @param workDir 工作目录
	 * @param fromFileSource 来源文件名，文件应在 workDir 目录下的文件夹
	 * @param toDataSource 去向
	 * @param config 设置
	 */
	public MigrationCommons(String workDir, String fromFileSource, DataSource toDataSource, MigrationConfig config) {
		this.config = config;
		this.workDir = workDir;
		this.fromFileSource = fromFileSource;
		this.toDataSource = toDataSource;
	}

	/**
	 * <p>方法名：getFromExtractResult</p>
	 * <p>描述：获取来源提取结果</p>
	 * @return
	 * @throws Exception 
	 */
	public MigrationExtractResult getFromExtractResult() 
			throws Exception {
		if (ValidateCommons.isEmpty(this.fromExtractResult)) {
			if (ValidateCommons.isNotEmpty(this.fromDataSource)) {
				this.fromExtractResult = extractor(this.fromDataSource, this.workDir, this.config);
			} else if (ValidateCommons.isNotEmpty(this.fromFileSource)) {
				this.fromExtractResult = new MigrationExtractResult(this.workDir, this.fromFileSource, this.config);
				this.fromExtractResult.readIn();
			}
		}
		return this.fromExtractResult;
	}

	/**
	 * <p>方法名：getToExtractResult</p>
	 * <p>描述：获取去向提取结果</p>
	 * @return
	 * @throws Exception 
	 */
	public MigrationExtractResult getToExtractResult() 
			throws Exception {
		if (ValidateCommons.isEmpty(this.toExtractResult)) {
			this.toExtractResult = extractor(this.toDataSource, this.workDir, this.config);
		}
		return this.toExtractResult;
	}

	/**
	 * <p>方法名：synchronizer</p>
	 * <p>描述：同步数据</p>
	 * @param compareResult 比较结果
	 * @throws Exception
	 */
	public void synchronizer(MigrationCompareResult compareResult) throws Exception {
		synchronizer(this.toDataSource, compareResult, getFromExtractResult());
	}

	/**
	 * <p>方法名：extractor</p>
	 * <p>描述：从 dataSource 按 config 将数据抽取到 workDir 下</p>
	 * @param dataSource 数据源
	 * @param workDir 工作目录
	 * @param config 迁移设置
	 * @return 抽取结果
	 * @throws Exception
	 */
	public static MigrationExtractResult extractor(DataSource dataSource, String workDir, MigrationConfig config) 
			throws Exception {
		MigrationExtractResult extractResult = new MigrationExtractResult(workDir, config);
		List<MigrationTableFilter> migrationTableFilters = config.getMigrationTableFilters();
		if (ValidateCommons.isNotEmpty(migrationTableFilters)) {
			MetaDataCommons metaDataCommons = new MetaDataCommons(dataSource);
			for (MigrationTableFilter migrationTableFilter : migrationTableFilters) {
				for (String tableNameFilter : migrationTableFilter.getTableNameFilters()) {
					List<DDLTable> tables = metaDataCommons.getDDLTables(migrationTableFilter.getTableSchemaFilter(), tableNameFilter, 
							migrationTableFilter.getTableTypeFilters(), migrationTableFilter.getTableInfoFilters());
					extractResult.addTables(tables);
				}
			}
		}
		return extractResult;
	}

	/**
	 * <p>方法名：synchronizer</p>
	 * <p>描述：将 extractResult 按 compareResult 同步到 dataSource</p>
	 * @param dataSource 数据源
	 * @param compareResult 比较结果
	 * @param extractResult 抽取结果
	 * @throws Exception
	 */
	public static void synchronizer(DataSource dataSource, MigrationCompareResult compareResult, MigrationExtractResult extractResult) 
			throws Exception {
		MigrationConfig config = extractResult.getConfig();
		boolean doSynchronizeBackup = config.isDoSynchronizeBackup();
		String backupExpression = config.getBackupExpression();
		List<DDLTableKey> tableKeys = extractResult.getTableKeys();
		Map<DDLTableKey, DDLTable> tablesMap = extractResult.getTablesMap();

		JdbcCommons jdbcCommons = new JdbcCommons(dataSource);
		MetaDataCommons metaDataCommons = jdbcCommons.getMetaDataCommons();
		DialectCommons dialectCommons = jdbcCommons.getDialectCommons();
		if (config.getSynchronizeScopes().contains(MigrationConfig.SCOPE_DEFINITION) && ValidateCommons.isNotEmpty(tableKeys)) {
			for (DDLTableKey tableKey : tableKeys) {
				DDLTable table = tablesMap.get(tableKey);

				if (doSynchronizeBackup && compareResult.existTable(tableKey)) {
					String tableName = table.getTableName();
					String cloneTableName = PlaceholderCommons.replacePlaceholder(backupExpression, new ParamMap<String, String>().putParam(MigrationConfig.BACKUP_EXPRESSION_KEY, tableName));
					DDLTable cloneTable = BeanCommons.cloneBySerializable(table);
					cloneTable.reset(null, backupExpression, backupExpression, backupExpression);

					metaDataCommons.createTable(cloneTable, true);
					jdbcCommons.executeCopyData(tableName, null, cloneTableName, null, null, null);
				}
				if (compareResult.dropTable(tableKey)) {
					metaDataCommons.dropTable(table, true);
				}
				if (compareResult.createTable(tableKey)) {
					metaDataCommons.createTable(table, true);
				}
			}
		}
		if (config.getExtractScopes().contains(MigrationConfig.SCOPE_DATA)) {
			String dataFolderPath = extractResult.getDataFolderPath();
			SqlIOConfig dataIOConfig = new SqlIOConfig();
			String charset = dataIOConfig.getCharset();
			DatabaseIOSource dataIOSource = new DatabaseIOSource(dataSource);
			SqlIOTemplate dataIO = new SqlIOTemplate(JSON.toJSONString(dataIOConfig), charset);
			MemoryIOSource bigDataIndexIOSource = new MemoryIOSource();
			JSONIOTemplate bigDataIndexIO = new JSONIOTemplate(FileCommons.appendPathNode(MigrationCommons.class.getResource("/").getPath(), MigrationExtractResult.BIG_DATA_INDEX_FILE_TEMPLATE));

			if (ValidateCommons.isEmpty(tableKeys)) {
				final List<DDLTableKey> _tableKeys = new ArrayList<DDLTableKey>();
				// 进入兼容模式，既未抽取表结构，直接从数据目录获取 .sql 文件列表
				FileCommons.listFolder(dataFolderPath, new FileFilter() {
					@Override
					public boolean accept(FileInfo fileInfo) {
						return MigrationExtractResult.ENLARGE_NAME_SQL.equals(StringCommons.toLowerCase(fileInfo.getType()));
					}
				}, new Callable<FileInfo, Void>() {
					@Override
					public Void call(FileInfo fileInfo) throws Exception {
						String name = fileInfo.getName(); // [schema].[tableName]
						String[] array = name.split(".");
						_tableKeys.add(new DDLTableKey(array[0], array[1]));
						return null;
					}
				});
				tableKeys = new ArrayList<DDLTableKey>(_tableKeys);
			}
			for (DDLTableKey tableKey : tableKeys) {
				String schema = tableKey.getSchema();
				String tableName = tableKey.getTableName();
				if (ValidateCommons.isNotEmpty(schema) && ValidateCommons.isNotEmpty(tableName)) {
					tableName = StringCommons.merger(schema, ".", tableName);
				}

				if (compareResult.clearData(tableKey)) {
					jdbcCommons.executeClearTable(tableName);
				}

				dataIO.input(dataIOSource, FileCommons.appendPathNode(dataFolderPath, StringCommons.merger(tableName, ".", MigrationExtractResult.ENLARGE_NAME_SQL)));

				bigDataIndexIOSource.clearInput();
				bigDataIndexIO.input(bigDataIndexIOSource, FileCommons.appendPathNode(dataFolderPath, StringCommons.merger(tableName, ".", MigrationExtractResult.ENLARGE_NAME_JSON)));
				List<Map<String, Object>> bigDataIndexs = bigDataIndexIOSource.getInputResult(MigrationExtractResult.IO_EXPRESSION_BIG_DATA_INDEX);
				if (ValidateCommons.isNotEmpty(bigDataIndexs)) {
					for (Map<String, Object> bigDataIndex : bigDataIndexs) {
						String updateStatement = (String) bigDataIndex.get(MigrationExtractResult.BIG_DATA_INDEX_KEY_UPDATE_STATEMENT);
						String fileName = (String) bigDataIndex.get(MigrationExtractResult.BIG_DATA_INDEX_KEY_FILE_NAME);
						String lobType = (String) bigDataIndex.get(MigrationExtractResult.BIG_DATA_INDEX_KEY_LOB_TYPE);

						String bigDataFile = FileCommons.appendPathNode(extractResult.getDataFolderPath(), fileName);
						if (dialectCommons.TYPE.BLOB.equals(lobType)) {
							jdbcCommons.getJdbcTemplate().update(updateStatement, StreamCommons.binaryReader(new FileInputStream(bigDataFile)));
						} else if (dialectCommons.TYPE.CLOB.equals(lobType)) {
							jdbcCommons.getJdbcTemplate().update(updateStatement, StreamCommons.textReader(new InputStreamReader(new FileInputStream(bigDataFile), charset)));
						}
					}
				}
			}
		}
	}

}
